Librerias
Primero incorporamos las librerias necesarias para poder ejecutar el presente notebook.
# Librerias
#install.packages('gganimate')
#install.packages("heatmaply")
#install.packages('gifski')
#install.packages('png')
#install.packages('units')
#install.packages('sf')
#install.packages('transformr')
require(tidyverse)
require(geosphere)
#<<<<<<< Updated upstream
require(rvest)
require(gganimate)
require(lubridate)
require(heatmaply)
require(transformr)
require(gifski)
require(png)
require(ggplot2)
#=======
require(rvest) # Cargamos el paquete
require(lubridate)
#>>>>>>> Stashed changes
Importar datos
Posteriormente importamos los datos que queremos analizar y los formateamos.
# Llamamos a los DataSets
datos_2021 = read.csv('../TP_LaboDeDatos/Data/sna_abril_2021_fixed_encoding.csv', encoding = 'UTF-8', sep = ',')
# Concateno todos los datos
datos2= read_csv2('../TP_LaboDeDatos/Data/202109-informe-ministerio.csv')
datos2$Fecha_completa = strptime(paste(datos2$Fecha, datos2$`Hora UTC`), format = "%d/%m/%Y %H:%M:%S")
En esa celda importamos los datos de los aeropuertos.
# Utilizamos la tabla que se encuentra en 'https://en.wikipedia.org/wiki/List_of_airports_in_Argentina'
# para acceder a las variables de ciudad, provincia y coordenadas de cada aeropuerto
aeropuertos_wiki = read_html('https://en.wikipedia.org/wiki/List_of_airports_in_Argentina')
elemento_tabla = html_element(aeropuertos_wiki,'.wikitable')
aeropuertos = html_table(elemento_tabla)
Particularmente, necesitamos un procesamiento especial para obtener las coordenadas de cada aeropuerto en un formato que nos sea util.
# Corrijo la columna de coordenadas
separar = strsplit((aeropuertos$Coordinates), '/') # Divide a los strings en los lugares donde haya '/'
coordenadas = sapply(separar, function(x) x[3]) # Me quedo solo con el 3er tipo de coordenada
coordenadas = gsub('[^0-9,.,-]','', coordenadas) # Elimino los caracteres que no quiero utilizar
aeropuertos = aeropuertos %>%
mutate(lat = as.numeric(substr(coordenadas, 1, 9)), long = as.numeric(substr(coordenadas, 10, 18))) # Separo a mano latitud y longitud (revizar si esta todo en orden)
aeropuertos = filter(aeropuertos, nchar(ICAO)>1)
aeropuertos = aeropuertos[order(aeropuertos$ICAO),]
Procesar datos
En la siguiente celda lo que hacemos es a cada vuelo le agregamos los datos relativos a sus aeropuertos de origen y destino (si los tenemos). Además, calculamos con ellos la distancia recorrida por el vuelo.
for(i in 1:length(aeropuertos$ICAO)){
inds = datos2$Aeropuerto==aeropuertos$IATA[i]
datos2[inds,c('ciudad_origen','provincia_origen','lat_origen','long_origen')] = aeropuertos[i,c("City served","Province","lat","long")]
inds = datos2$`Origen / Destino`==aeropuertos$IATA[i]
datos2[inds,c('ciudad_destino','provincia_destino','lat_destino','long_destino')] = aeropuertos[i,c("City served","Province","lat","long")]
}
for(i in 1:length(datos_2021$ana)){
inds = datos2$Aeropuerto==datos_2021$ana[i]
datos2[inds,c('ciudad_origen','provincia_origen','lat_origen','long_origen')] = datos_2021[i,c("nam","cpr","x","y")]
inds = datos2$`Origen / Destino`==datos_2021$ana[i]
datos2[inds,c('ciudad_destino','provincia_destino','lat_destino','long_destino')] = datos_2021[i,c("nam","cpr","x","y")]
inds = datos2$Aeropuerto==datos_2021$iko[i]
datos2[inds,c('ciudad_destino','provincia_destino','lat_destino','long_destino')] = datos_2021[i,c("nam","cpr","x","y")]
inds = datos2$`Origen / Destino`==datos_2021$iko[i]
datos2[inds,c('ciudad_destino','provincia_destino','lat_destino','long_destino')] = datos_2021[i,c("nam","cpr","x","y")]
}
datos2 = drop_na(datos2)
datos2$distancia = distHaversine(datos2[,c("long_origen","lat_origen")],datos2[,c("long_destino","lat_destino")])/1000
datos2 = datos2 %>%
filter(`Aerolinea Nombre` != 0)
Dividimos los eventos del dataset de vuelos en aterrizajes y despegues.
datos2$fecha_hora = strptime(paste(datos2$Fecha, datos2$`Hora UTC`), format = "%d/%m/%Y %H:%M:%S")
despegues = datos2[datos2$`Tipo de Movimiento` == 'Despegue',]
aterrizajes = datos2[datos2$`Tipo de Movimiento` == 'Aterrizaje',]
Ahora tratamos de unir los aterrizajes y despegues con el objetivo de poder identificar todo el recorrido de un vuelo. (particularmente la hora de salida y de llegada, que son los datos que no nos dan directamente en el dataset)
# dividimos en despegues y aterrizajes
matched = left_join(despegues, aterrizajes, by= c("Aeropuerto" = "Origen / Destino", "Origen / Destino" = "Aeropuerto", "Aerolinea Nombre" = "Aerolinea Nombre", "Aeronave" = "Aeronave")) %>%
mutate(tdif = as.numeric(fecha_hora.y - fecha_hora.x, units='hours')) %>%
group_by(Aeropuerto, fecha_hora.x, Aeronave, `Aerolinea Nombre`) %>%
filter(tdif > 0) %>%
filter(tdif < 5) %>%
filter(tdif == min(tdif))
matched = matched %>%
mutate(vel_media = distancia.y/tdif)
Preguntas
Concentración del mercado
Las primeras preguntas que nos hemos planteado son relativas al grado de concentración que tienen los vuelos.
Por esto, la primera pregunta que nos interesa responder es ver cuales aeropuertos se conectan más y cuales menos. Además, notando que los casos donde el vuelo despega y aterriza en el mismo lugar son muy posiblemente vuelos de entrenamiento.
aers = unique(datos2$Aeropuerto)
mat = matrix(nrow = length(aers), ncol = length(aers))
colnames(mat) = aers
row.names(mat) = aers
for(i in 1:length(aers)){
for(j in 1:length(aers)){
mat[i,j] = sum((datos2$Aeropuerto==aers[i]) & (datos2$`Origen / Destino`==aers[j]))
}
}
heatmaply(mat, Rowv = NA, Colv = NA, col_dend_up=FALSE)%>%
layout(xaxis = list(side = "top"))
Resalta el aeropuerto FDO como un importante centro de entrenamiento. Por otro lado, se observa como la conexión más fuerte es claramente entre Aeroparque y Bariloche.
La siguiente pregunta será relativa a la concentración entre distintas aerolineas.
aerolineas = unique(matched$`Aerolinea Nombre`)
apps = matrix(nrow = 1, ncol = length(aerolineas))
colnames(apps) = aerolineas
for(ar in aerolineas){
apps[1,ar] = sum((matched$`Aerolinea Nombre`==ar))
}
apps = apps[1,order(apps, decreasing=T)]
barplot(apps, las = 2, cex.names=0.4)
En este segundo grafico solo mostramos las más importantes para que sea entendible la información, además de que tiene una función más sintetica que el anterior.
x = 8
apps2 = apps[1:x]
apps2[x+1] = sum(apps[x:length(apps)])
names(apps2)[x+1] = "OTRAS"
names(apps2) = paste(names(apps2),apps2)
fig <- plot_ly(data = as.data.frame(apps2), labels = names(apps2), values = apps2, type = 'pie')
fig <- fig %>% layout(title = 'Cantidad de vuelos por aerolinea',
xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))
fig
Tiempo en el aire
Las siguientes preguntas que nos hemos planteado feron las relativas a cuando los aviones estaban volando y cuando no.
La primera fue decir exactamente en qué horas del año estuvieron más y menos aviones en el aire. Para eso primero hay que agregar una hora universal a los vuelos
inicio = as.Date("2021-01-01")
matched$despegueUniversal = as.integer(-difftime(inicio,matched$Fecha_completa.x, units="hours"))
matched$llegadaUniversal = as.integer(-difftime(inicio,matched$Fecha_completa.y, units="hours")+0.9999)
fin = max(matched$llegadaUniversal)+1
histo = matrix(nrow=1,ncol=fin)
histo[,c(1:fin)] = 0
for(i in 1:length(matched$despegueUniversal)){
inds = matched$despegueUniversal[i]:matched$llegadaUniversal[i]
histo[1,inds] = histo[1,inds]+1
#print(histo[1,inds])
#for(j in matched$despegueUniversal[i]:matched$llegadaUniversal[i]){
# histo[1,j] = histo[1,j]+1
#}
}
#plot(1:length(histo[1,]),histo[1,], type="s")
barplot(histo[1,], names.arg=(inicio + 1:fin/24), las = 2 , cex.names=0.4)
Ahora cual es el horario en el que los pasajeros viajan mas
require(gganimate)
horario_vuelo = datos2 %>%
filter(datos2$Pasajeros > 0) %>%
group_by(horario = hour(`Hora UTC`)) %>%
summarise(pasajeros = mean(Pasajeros))
table(horario_vuelo$horario)
plot = horario_vuelo %>%
ggplot(aes(x = horario, y = pasajeros, color = pasajeros)) + geom_line() +
geom_point() +
transition_reveal(horario)+labs(title = 'Cantidad promedio de pasajeros a lo largo del dia', x = 'Hora', y = 'Cantidad promedio de pasajeros')+ shadow_mark()+ scale_x_continuous(breaks = round(seq(min(horario_vuelo$horario), max(horario_vuelo$horario), by = 1),1))
animate(plot, renderer = gifski_renderer(loop = T))
plot <- horario_vuelo %>%
ggplot(aes(x = horario, y = pasajeros, fill = pasajeros)) + geom_bar(stat='identity')+transition_states(horario,100,5)+shadow_mark()+labs(title = 'Cantidad promedio de pasajeros a lo largo del dia', x = 'Hora', y = 'Cantidad promedio de pasajeros')+ scale_x_continuous(breaks = round(seq(min(horario_vuelo$horario), max(horario_vuelo$horario), by = 1),1))
animate(plot, renderer = gifski_renderer(loop = T))
LS0tDQp0aXRsZTogIlRyYWJham8gUHJhY3RpY28gLSBMYWJvcmF0b3JpbyBkZSBEYXRvcyINCmF1dGhvcjogIk1vcmFsZXMgSm9hcXVpbiwgTGFzb3JzYSBMYXV0YXJvIHkgUGFsYXp6byBUb21hcyINCmRhdGU6ICIxNC8xMC8yMDIxIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBsdW1lbg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0Kc3VidGl0bGU6ICJMYWJvcmF0b3JpbyBkZSBEYXRvcyINCi0tLQ0KIyBMaWJyZXJpYXMNClByaW1lcm8gaW5jb3Jwb3JhbW9zIGxhcyBsaWJyZXJpYXMgbmVjZXNhcmlhcyBwYXJhIHBvZGVyIGVqZWN1dGFyIGVsIHByZXNlbnRlIG5vdGVib29rLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgTGlicmVyaWFzDQojaW5zdGFsbC5wYWNrYWdlcygnZ2dhbmltYXRlJykNCiNpbnN0YWxsLnBhY2thZ2VzKCJoZWF0bWFwbHkiKQ0KI2luc3RhbGwucGFja2FnZXMoJ2dpZnNraScpDQojaW5zdGFsbC5wYWNrYWdlcygncG5nJykNCiNpbnN0YWxsLnBhY2thZ2VzKCd1bml0cycpDQojaW5zdGFsbC5wYWNrYWdlcygnc2YnKQ0KI2luc3RhbGwucGFja2FnZXMoJ3RyYW5zZm9ybXInKQ0KcmVxdWlyZSh0aWR5dmVyc2UpDQpyZXF1aXJlKGdlb3NwaGVyZSkNCiM8PDw8PDw8IFVwZGF0ZWQgdXBzdHJlYW0NCnJlcXVpcmUocnZlc3QpDQpyZXF1aXJlKGdnYW5pbWF0ZSkNCnJlcXVpcmUobHVicmlkYXRlKQ0KcmVxdWlyZShoZWF0bWFwbHkpDQpyZXF1aXJlKHRyYW5zZm9ybXIpDQpyZXF1aXJlKGdpZnNraSkNCnJlcXVpcmUocG5nKQ0KcmVxdWlyZShnZ3Bsb3QyKQ0KIz09PT09PT0NCnJlcXVpcmUocnZlc3QpICMgQ2FyZ2Ftb3MgZWwgcGFxdWV0ZQ0KcmVxdWlyZShsdWJyaWRhdGUpDQojPj4+Pj4+PiBTdGFzaGVkIGNoYW5nZXMNCmBgYA0KIyBJbXBvcnRhciBkYXRvcw0KDQpQb3N0ZXJpb3JtZW50ZSBpbXBvcnRhbW9zIGxvcyBkYXRvcyBxdWUgcXVlcmVtb3MgYW5hbGl6YXIgeSBsb3MgZm9ybWF0ZWFtb3MuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIExsYW1hbW9zIGEgbG9zIERhdGFTZXRzDQoNCmRhdG9zXzIwMjEgPSByZWFkLmNzdignLi4vVFBfTGFib0RlRGF0b3MvRGF0YS9zbmFfYWJyaWxfMjAyMV9maXhlZF9lbmNvZGluZy5jc3YnLCBlbmNvZGluZyA9ICdVVEYtOCcsIHNlcCA9ICcsJykNCg0KIyBDb25jYXRlbm8gdG9kb3MgbG9zIGRhdG9zDQpkYXRvczI9IHJlYWRfY3N2MignLi4vVFBfTGFib0RlRGF0b3MvRGF0YS8yMDIxMDktaW5mb3JtZS1taW5pc3RlcmlvLmNzdicpDQoNCmRhdG9zMiRGZWNoYV9jb21wbGV0YSA9IHN0cnB0aW1lKHBhc3RlKGRhdG9zMiRGZWNoYSwgZGF0b3MyJGBIb3JhIFVUQ2ApLCBmb3JtYXQgPSAiJWQvJW0vJVkgJUg6JU06JVMiKQ0KDQpgYGANCg0KRW4gZXNhIGNlbGRhIGltcG9ydGFtb3MgbG9zIGRhdG9zIGRlIGxvcyBhZXJvcHVlcnRvcy4NCmBgYHtyfQ0KIyBVdGlsaXphbW9zIGxhIHRhYmxhIHF1ZSBzZSBlbmN1ZW50cmEgZW4gJ2h0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xpc3Rfb2ZfYWlycG9ydHNfaW5fQXJnZW50aW5hJw0KIyBwYXJhIGFjY2VkZXIgYSBsYXMgdmFyaWFibGVzIGRlIGNpdWRhZCwgcHJvdmluY2lhIHkgY29vcmRlbmFkYXMgZGUgY2FkYSBhZXJvcHVlcnRvIA0KDQphZXJvcHVlcnRvc193aWtpID0gcmVhZF9odG1sKCdodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX2FpcnBvcnRzX2luX0FyZ2VudGluYScpDQplbGVtZW50b190YWJsYSAgID0gaHRtbF9lbGVtZW50KGFlcm9wdWVydG9zX3dpa2ksJy53aWtpdGFibGUnKQ0KYWVyb3B1ZXJ0b3MgICAgICA9IGh0bWxfdGFibGUoZWxlbWVudG9fdGFibGEpDQoNCmBgYA0KDQpQYXJ0aWN1bGFybWVudGUsIG5lY2VzaXRhbW9zIHVuIHByb2Nlc2FtaWVudG8gZXNwZWNpYWwgcGFyYSBvYnRlbmVyIGxhcyBjb29yZGVuYWRhcyBkZSBjYWRhIGFlcm9wdWVydG8gZW4gdW4gZm9ybWF0byBxdWUgbm9zIHNlYSB1dGlsLg0KDQpgYGB7cn0NCiMgQ29ycmlqbyBsYSBjb2x1bW5hIGRlIGNvb3JkZW5hZGFzIA0KDQpzZXBhcmFyICAgICA9IHN0cnNwbGl0KChhZXJvcHVlcnRvcyRDb29yZGluYXRlcyksICcvJykgIyBEaXZpZGUgYSBsb3Mgc3RyaW5ncyBlbiBsb3MgbHVnYXJlcyBkb25kZSBoYXlhICcvJyANCmNvb3JkZW5hZGFzID0gc2FwcGx5KHNlcGFyYXIsIGZ1bmN0aW9uKHgpIHhbM10pICAgICAgICAjIE1lIHF1ZWRvIHNvbG8gY29uIGVsIDNlciB0aXBvIGRlIGNvb3JkZW5hZGENCmNvb3JkZW5hZGFzID0gZ3N1YignW14wLTksLiwtXScsJycsIGNvb3JkZW5hZGFzKSAgICAgICAgICAgIyBFbGltaW5vIGxvcyBjYXJhY3RlcmVzIHF1ZSBubyBxdWllcm8gdXRpbGl6YXINCg0KYWVyb3B1ZXJ0b3MgPSBhZXJvcHVlcnRvcyAlPiUgDQogIG11dGF0ZShsYXQgPSBhcy5udW1lcmljKHN1YnN0cihjb29yZGVuYWRhcywgMSwgOSkpLCBsb25nID0gYXMubnVtZXJpYyhzdWJzdHIoY29vcmRlbmFkYXMsIDEwLCAxOCkpKSAjIFNlcGFybyBhIG1hbm8gbGF0aXR1ZCB5IGxvbmdpdHVkIChyZXZpemFyIHNpIGVzdGEgdG9kbyBlbiBvcmRlbikNCg0KYWVyb3B1ZXJ0b3MgPSBmaWx0ZXIoYWVyb3B1ZXJ0b3MsIG5jaGFyKElDQU8pPjEpDQphZXJvcHVlcnRvcyA9IGFlcm9wdWVydG9zW29yZGVyKGFlcm9wdWVydG9zJElDQU8pLF0NCmBgYA0KDQojIFByb2Nlc2FyIGRhdG9zDQoNCkVuIGxhIHNpZ3VpZW50ZSBjZWxkYSBsbyBxdWUgaGFjZW1vcyBlcyBhIGNhZGEgdnVlbG8gbGUgYWdyZWdhbW9zIGxvcyBkYXRvcyByZWxhdGl2b3MgYSBzdXMgYWVyb3B1ZXJ0b3MgZGUgb3JpZ2VuIHkgZGVzdGlubyAoc2kgbG9zIHRlbmVtb3MpLiBBZGVtw6FzLCBjYWxjdWxhbW9zIGNvbiBlbGxvcyBsYSBkaXN0YW5jaWEgcmVjb3JyaWRhIHBvciBlbCB2dWVsby4NCg0KYGBge3J9DQoNCmZvcihpIGluIDE6bGVuZ3RoKGFlcm9wdWVydG9zJElDQU8pKXsNCiAgaW5kcyA9IGRhdG9zMiRBZXJvcHVlcnRvPT1hZXJvcHVlcnRvcyRJQVRBW2ldDQogIGRhdG9zMltpbmRzLGMoJ2NpdWRhZF9vcmlnZW4nLCdwcm92aW5jaWFfb3JpZ2VuJywnbGF0X29yaWdlbicsJ2xvbmdfb3JpZ2VuJyldID0gYWVyb3B1ZXJ0b3NbaSxjKCJDaXR5wqBzZXJ2ZWQiLCJQcm92aW5jZSIsImxhdCIsImxvbmciKV0NCiAgDQogIGluZHMgPSBkYXRvczIkYE9yaWdlbiAvIERlc3Rpbm9gPT1hZXJvcHVlcnRvcyRJQVRBW2ldDQogIGRhdG9zMltpbmRzLGMoJ2NpdWRhZF9kZXN0aW5vJywncHJvdmluY2lhX2Rlc3Rpbm8nLCdsYXRfZGVzdGlubycsJ2xvbmdfZGVzdGlubycpXSA9IGFlcm9wdWVydG9zW2ksYygiQ2l0ecKgc2VydmVkIiwiUHJvdmluY2UiLCJsYXQiLCJsb25nIildDQp9DQogIGZvcihpIGluIDE6bGVuZ3RoKGRhdG9zXzIwMjEkYW5hKSl7DQogIGluZHMgPSBkYXRvczIkQWVyb3B1ZXJ0bz09ZGF0b3NfMjAyMSRhbmFbaV0NCiAgZGF0b3MyW2luZHMsYygnY2l1ZGFkX29yaWdlbicsJ3Byb3ZpbmNpYV9vcmlnZW4nLCdsYXRfb3JpZ2VuJywnbG9uZ19vcmlnZW4nKV0gPSBkYXRvc18yMDIxW2ksYygibmFtIiwiY3ByIiwieCIsInkiKV0NCiAgDQogIA0KICBpbmRzID0gZGF0b3MyJGBPcmlnZW4gLyBEZXN0aW5vYD09ZGF0b3NfMjAyMSRhbmFbaV0NCiAgZGF0b3MyW2luZHMsYygnY2l1ZGFkX2Rlc3Rpbm8nLCdwcm92aW5jaWFfZGVzdGlubycsJ2xhdF9kZXN0aW5vJywnbG9uZ19kZXN0aW5vJyldID0gZGF0b3NfMjAyMVtpLGMoIm5hbSIsImNwciIsIngiLCJ5IildDQogIA0KICBpbmRzID0gZGF0b3MyJEFlcm9wdWVydG89PWRhdG9zXzIwMjEkaWtvW2ldDQogIGRhdG9zMltpbmRzLGMoJ2NpdWRhZF9kZXN0aW5vJywncHJvdmluY2lhX2Rlc3Rpbm8nLCdsYXRfZGVzdGlubycsJ2xvbmdfZGVzdGlubycpXSA9IGRhdG9zXzIwMjFbaSxjKCJuYW0iLCJjcHIiLCJ4IiwieSIpXQ0KICANCiAgaW5kcyA9IGRhdG9zMiRgT3JpZ2VuIC8gRGVzdGlub2A9PWRhdG9zXzIwMjEkaWtvW2ldDQogIGRhdG9zMltpbmRzLGMoJ2NpdWRhZF9kZXN0aW5vJywncHJvdmluY2lhX2Rlc3Rpbm8nLCdsYXRfZGVzdGlubycsJ2xvbmdfZGVzdGlubycpXSA9IGRhdG9zXzIwMjFbaSxjKCJuYW0iLCJjcHIiLCJ4IiwieSIpXQ0KfQ0KDQpkYXRvczIgPSBkcm9wX25hKGRhdG9zMikNCmRhdG9zMiRkaXN0YW5jaWEgPSBkaXN0SGF2ZXJzaW5lKGRhdG9zMlssYygibG9uZ19vcmlnZW4iLCJsYXRfb3JpZ2VuIildLGRhdG9zMlssYygibG9uZ19kZXN0aW5vIiwibGF0X2Rlc3Rpbm8iKV0pLzEwMDANCg0KZGF0b3MyID0gZGF0b3MyICU+JSANCiAgZmlsdGVyKGBBZXJvbGluZWEgTm9tYnJlYCAhPSAwKQ0KYGBgDQoNCkRpdmlkaW1vcyBsb3MgZXZlbnRvcyBkZWwgZGF0YXNldCBkZSB2dWVsb3MgZW4gYXRlcnJpemFqZXMgeSBkZXNwZWd1ZXMuDQoNCmBgYHtyfQ0KDQpkYXRvczIkZmVjaGFfaG9yYSA9IHN0cnB0aW1lKHBhc3RlKGRhdG9zMiRGZWNoYSwgZGF0b3MyJGBIb3JhIFVUQ2ApLCBmb3JtYXQgPSAiJWQvJW0vJVkgJUg6JU06JVMiKQ0KZGVzcGVndWVzID0gZGF0b3MyW2RhdG9zMiRgVGlwbyBkZSBNb3ZpbWllbnRvYCA9PSAnRGVzcGVndWUnLF0NCmF0ZXJyaXphamVzID0gZGF0b3MyW2RhdG9zMiRgVGlwbyBkZSBNb3ZpbWllbnRvYCA9PSAnQXRlcnJpemFqZScsXQ0KDQpgYGANCg0KQWhvcmEgdHJhdGFtb3MgZGUgdW5pciBsb3MgYXRlcnJpemFqZXMgeSBkZXNwZWd1ZXMgY29uIGVsIG9iamV0aXZvIGRlIHBvZGVyIGlkZW50aWZpY2FyIHRvZG8gZWwgcmVjb3JyaWRvIGRlIHVuIHZ1ZWxvLiAocGFydGljdWxhcm1lbnRlIGxhIGhvcmEgZGUgc2FsaWRhIHkgZGUgbGxlZ2FkYSwgcXVlIHNvbiBsb3MgZGF0b3MgcXVlIG5vIG5vcyBkYW4gZGlyZWN0YW1lbnRlIGVuIGVsIGRhdGFzZXQpDQoNCmBgYHtyfQ0KIyBkaXZpZGltb3MgZW4gZGVzcGVndWVzIHkgYXRlcnJpemFqZXMNCg0KDQptYXRjaGVkID0gbGVmdF9qb2luKGRlc3BlZ3VlcywgYXRlcnJpemFqZXMsIGJ5PSBjKCJBZXJvcHVlcnRvIiA9ICJPcmlnZW4gLyBEZXN0aW5vIiwgIk9yaWdlbiAvIERlc3Rpbm8iID0gIkFlcm9wdWVydG8iLCAiQWVyb2xpbmVhIE5vbWJyZSIgPSAiQWVyb2xpbmVhIE5vbWJyZSIsICJBZXJvbmF2ZSIgPSAiQWVyb25hdmUiKSkgJT4lIA0KICBtdXRhdGUodGRpZiA9IGFzLm51bWVyaWMoZmVjaGFfaG9yYS55IC0gZmVjaGFfaG9yYS54LCB1bml0cz0naG91cnMnKSkgJT4lDQogIGdyb3VwX2J5KEFlcm9wdWVydG8sIGZlY2hhX2hvcmEueCwgQWVyb25hdmUsIGBBZXJvbGluZWEgTm9tYnJlYCkgJT4lDQogIGZpbHRlcih0ZGlmID4gMCkgJT4lIA0KICBmaWx0ZXIodGRpZiA8IDUpICU+JSANCiAgZmlsdGVyKHRkaWYgPT0gbWluKHRkaWYpKQ0KDQptYXRjaGVkID0gbWF0Y2hlZCAlPiUNCiAgbXV0YXRlKHZlbF9tZWRpYSA9IGRpc3RhbmNpYS55L3RkaWYpDQoNCmBgYA0KIyBQcmVndW50YXMNCiMjIyBDb25jZW50cmFjacOzbiBkZWwgbWVyY2Fkbw0KTGFzIHByaW1lcmFzIHByZWd1bnRhcyBxdWUgbm9zIGhlbW9zIHBsYW50ZWFkbyBzb24gcmVsYXRpdmFzIGFsIGdyYWRvIGRlIGNvbmNlbnRyYWNpw7NuIHF1ZSB0aWVuZW4gbG9zIHZ1ZWxvcy4NCg0KUG9yIGVzdG8sIGxhIHByaW1lcmEgcHJlZ3VudGEgcXVlIG5vcyBpbnRlcmVzYSByZXNwb25kZXIgZXMgdmVyIGN1YWxlcyBhZXJvcHVlcnRvcyBzZSBjb25lY3RhbiBtw6FzIHkgY3VhbGVzIG1lbm9zLiBBZGVtw6FzLCBub3RhbmRvIHF1ZSBsb3MgY2Fzb3MgZG9uZGUgZWwgdnVlbG8gZGVzcGVnYSB5IGF0ZXJyaXphIGVuIGVsIG1pc21vIGx1Z2FyIHNvbiBtdXkgcG9zaWJsZW1lbnRlIHZ1ZWxvcyBkZSBlbnRyZW5hbWllbnRvLg0KYGBge3J9DQphZXJzID0gdW5pcXVlKGRhdG9zMiRBZXJvcHVlcnRvKQ0KbWF0ID0gbWF0cml4KG5yb3cgPSBsZW5ndGgoYWVycyksIG5jb2wgPSBsZW5ndGgoYWVycykpDQpjb2xuYW1lcyhtYXQpID0gYWVycw0Kcm93Lm5hbWVzKG1hdCkgPSBhZXJzDQpmb3IoaSBpbiAxOmxlbmd0aChhZXJzKSl7DQogIGZvcihqIGluIDE6bGVuZ3RoKGFlcnMpKXsNCiAgICBtYXRbaSxqXSAgPSAgc3VtKChkYXRvczIkQWVyb3B1ZXJ0bz09YWVyc1tpXSkgJiAoZGF0b3MyJGBPcmlnZW4gLyBEZXN0aW5vYD09YWVyc1tqXSkpDQogIH0NCn0NCmhlYXRtYXBseShtYXQsIFJvd3YgPSBOQSwgQ29sdiA9IE5BLCAgY29sX2RlbmRfdXA9RkFMU0UpJT4lIA0KICBsYXlvdXQoeGF4aXMgPSBsaXN0KHNpZGUgPSAidG9wIikpDQpgYGANClJlc2FsdGEgZWwgYWVyb3B1ZXJ0byBGRE8gY29tbyB1biBpbXBvcnRhbnRlIGNlbnRybyBkZSBlbnRyZW5hbWllbnRvLiBQb3Igb3RybyBsYWRvLCBzZSBvYnNlcnZhIGNvbW8gbGEgY29uZXhpw7NuIG3DoXMgZnVlcnRlIGVzIGNsYXJhbWVudGUgZW50cmUgQWVyb3BhcnF1ZSB5IEJhcmlsb2NoZS4gDQoNCkxhIHNpZ3VpZW50ZSBwcmVndW50YSBzZXLDoSByZWxhdGl2YSBhIGxhIGNvbmNlbnRyYWNpw7NuIGVudHJlIGRpc3RpbnRhcyBhZXJvbGluZWFzLiANCg0KYGBge3J9DQphZXJvbGluZWFzID0gdW5pcXVlKG1hdGNoZWQkYEFlcm9saW5lYSBOb21icmVgKQ0KYXBwcyA9IG1hdHJpeChucm93ID0gMSwgbmNvbCA9IGxlbmd0aChhZXJvbGluZWFzKSkNCmNvbG5hbWVzKGFwcHMpID0gYWVyb2xpbmVhcw0KZm9yKGFyIGluIGFlcm9saW5lYXMpew0KICBhcHBzWzEsYXJdID0gc3VtKChtYXRjaGVkJGBBZXJvbGluZWEgTm9tYnJlYD09YXIpKQ0KfQ0KYXBwcyA9IGFwcHNbMSxvcmRlcihhcHBzLCBkZWNyZWFzaW5nPVQpXQ0KYmFycGxvdChhcHBzLCBsYXMgPSAyLCBjZXgubmFtZXM9MC40KQ0KYGBgDQoNCkVuIGVzdGUgc2VndW5kbyBncmFmaWNvIHNvbG8gbW9zdHJhbW9zIGxhcyBtw6FzIGltcG9ydGFudGVzIHBhcmEgcXVlIHNlYSBlbnRlbmRpYmxlIGxhIGluZm9ybWFjacOzbiwgYWRlbcOhcyBkZSBxdWUgdGllbmUgdW5hIGZ1bmNpw7NuIG3DoXMgc2ludGV0aWNhIHF1ZSBlbCBhbnRlcmlvci4gDQoNCmBgYHtyfQ0KeCA9IDgNCmFwcHMyID0gYXBwc1sxOnhdDQphcHBzMlt4KzFdID0gc3VtKGFwcHNbeDpsZW5ndGgoYXBwcyldKQ0KbmFtZXMoYXBwczIpW3grMV0gPSAiT1RSQVMiDQpuYW1lcyhhcHBzMikgPSBwYXN0ZShuYW1lcyhhcHBzMiksYXBwczIpDQoNCg0KZmlnIDwtIHBsb3RfbHkoZGF0YSA9IGFzLmRhdGEuZnJhbWUoYXBwczIpLCBsYWJlbHMgPSBuYW1lcyhhcHBzMiksIHZhbHVlcyA9IGFwcHMyLCB0eXBlID0gJ3BpZScpDQpmaWcgPC0gZmlnICU+JSBsYXlvdXQodGl0bGUgPSAnQ2FudGlkYWQgZGUgdnVlbG9zIHBvciBhZXJvbGluZWEnLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpKQ0KDQpmaWcNCmBgYA0KDQojIyMgVGllbXBvIGVuIGVsIGFpcmUNCkxhcyBzaWd1aWVudGVzIHByZWd1bnRhcyBxdWUgbm9zIGhlbW9zIHBsYW50ZWFkbyBmZXJvbiBsYXMgcmVsYXRpdmFzIGEgY3VhbmRvIGxvcyBhdmlvbmVzIGVzdGFiYW4gdm9sYW5kbyB5IGN1YW5kbyBuby4NCg0KTGEgcHJpbWVyYSBmdWUgZGVjaXIgZXhhY3RhbWVudGUgZW4gcXXDqSBob3JhcyBkZWwgYcOxbyBlc3R1dmllcm9uIG3DoXMgeSBtZW5vcyBhdmlvbmVzIGVuIGVsIGFpcmUuIFBhcmEgZXNvIHByaW1lcm8gaGF5IHF1ZSBhZ3JlZ2FyIHVuYSBob3JhIHVuaXZlcnNhbCBhIGxvcyB2dWVsb3MNCg0KYGBge3J9DQppbmljaW8gPSBhcy5EYXRlKCIyMDIxLTAxLTAxIikNCm1hdGNoZWQkZGVzcGVndWVVbml2ZXJzYWwgPSBhcy5pbnRlZ2VyKC1kaWZmdGltZShpbmljaW8sbWF0Y2hlZCRGZWNoYV9jb21wbGV0YS54LCB1bml0cz0iaG91cnMiKSkNCm1hdGNoZWQkbGxlZ2FkYVVuaXZlcnNhbCA9IGFzLmludGVnZXIoLWRpZmZ0aW1lKGluaWNpbyxtYXRjaGVkJEZlY2hhX2NvbXBsZXRhLnksIHVuaXRzPSJob3VycyIpKzAuOTk5OSkNCmZpbiA9IG1heChtYXRjaGVkJGxsZWdhZGFVbml2ZXJzYWwpKzENCmhpc3RvID0gbWF0cml4KG5yb3c9MSxuY29sPWZpbikNCmhpc3RvWyxjKDE6ZmluKV0gPSAwDQpmb3IoaSBpbiAxOmxlbmd0aChtYXRjaGVkJGRlc3BlZ3VlVW5pdmVyc2FsKSl7DQogIGluZHMgPSBtYXRjaGVkJGRlc3BlZ3VlVW5pdmVyc2FsW2ldOm1hdGNoZWQkbGxlZ2FkYVVuaXZlcnNhbFtpXQ0KICBoaXN0b1sxLGluZHNdID0gaGlzdG9bMSxpbmRzXSsxDQogICNwcmludChoaXN0b1sxLGluZHNdKQ0KICAjZm9yKGogaW4gbWF0Y2hlZCRkZXNwZWd1ZVVuaXZlcnNhbFtpXTptYXRjaGVkJGxsZWdhZGFVbml2ZXJzYWxbaV0pew0KICAjICBoaXN0b1sxLGpdID0gaGlzdG9bMSxqXSsxDQogICN9DQp9DQojcGxvdCgxOmxlbmd0aChoaXN0b1sxLF0pLGhpc3RvWzEsXSwgdHlwZT0icyIpDQpiYXJwbG90KGhpc3RvWzEsXSwgbmFtZXMuYXJnPShpbmljaW8gKyAxOmZpbi8yNCksIGxhcyA9IDIgLCBjZXgubmFtZXM9MC40KQ0KDQoNCmBgYA0KDQoNCkFob3JhIGN1YWwgZXMgZWwgaG9yYXJpbyBlbiBlbCBxdWUgbG9zIHBhc2FqZXJvcyB2aWFqYW4gbWFzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpyZXF1aXJlKGdnYW5pbWF0ZSkNCmhvcmFyaW9fdnVlbG8gPSBkYXRvczIgJT4lDQogIGZpbHRlcihkYXRvczIkUGFzYWplcm9zID4gMCkgJT4lIA0KICBncm91cF9ieShob3JhcmlvID0gaG91cihgSG9yYSBVVENgKSkgJT4lIA0KICBzdW1tYXJpc2UocGFzYWplcm9zID0gbWVhbihQYXNhamVyb3MpKQ0KDQp0YWJsZShob3JhcmlvX3Z1ZWxvJGhvcmFyaW8pDQoNCnBsb3QgPSBob3JhcmlvX3Z1ZWxvICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gaG9yYXJpbywgeSA9IHBhc2FqZXJvcywgY29sb3IgPSBwYXNhamVyb3MpKSArIGdlb21fbGluZSgpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICB0cmFuc2l0aW9uX3JldmVhbChob3JhcmlvKStsYWJzKHRpdGxlID0gJ0NhbnRpZGFkIHByb21lZGlvIGRlIHBhc2FqZXJvcyBhIGxvIGxhcmdvIGRlbCBkaWEnLCB4ID0gJ0hvcmEnLCB5ID0gJ0NhbnRpZGFkIHByb21lZGlvIGRlIHBhc2FqZXJvcycpKyBzaGFkb3dfbWFyaygpKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKG1pbihob3JhcmlvX3Z1ZWxvJGhvcmFyaW8pLCBtYXgoaG9yYXJpb192dWVsbyRob3JhcmlvKSwgYnkgPSAxKSwxKSkNCmFuaW1hdGUocGxvdCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIobG9vcCA9IFQpKQ0KDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3QgPC0gaG9yYXJpb192dWVsbyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGhvcmFyaW8sIHkgPSBwYXNhamVyb3MsIGZpbGwgPSBwYXNhamVyb3MpKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykrdHJhbnNpdGlvbl9zdGF0ZXMoaG9yYXJpbywxMDAsNSkrc2hhZG93X21hcmsoKStsYWJzKHRpdGxlID0gJ0NhbnRpZGFkIHByb21lZGlvIGRlIHBhc2FqZXJvcyBhIGxvIGxhcmdvIGRlbCBkaWEnLCB4ID0gJ0hvcmEnLCB5ID0gJ0NhbnRpZGFkIHByb21lZGlvIGRlIHBhc2FqZXJvcycpKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKG1pbihob3JhcmlvX3Z1ZWxvJGhvcmFyaW8pLCBtYXgoaG9yYXJpb192dWVsbyRob3JhcmlvKSwgYnkgPSAxKSwxKSkNCmFuaW1hdGUocGxvdCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIobG9vcCA9IFQpKQ0KDQoNCmBgYA==